package cn.springsoter.core.redis.serializer;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.type.TypeFactory;

import cn.springsoter.core.tool.utils.StringPool;
import lombok.extern.slf4j.Slf4j;

/**
 * 将redis value序列化为json
 *
 * @author kevin
 */
@Slf4j
public class RedisJsonValueSerializer<T> implements RedisSerializer<T> {
	public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
	static final byte[] EMPTY_ARRAY = new byte[0];
	
	private final JavaType javaType;

	private MyObjectMapper objectMapper = new MyObjectMapper();

	/**
	 * Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link Class}.
	 *
	 * @param type
	 */
	public RedisJsonValueSerializer(Class<T> type) {
		setObjectMapperProps();
		this.javaType = getJavaType(type);
	}

	/**
	 * Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link JavaType}.
	 *
	 * @param javaType
	 */
	public RedisJsonValueSerializer(JavaType javaType) {
		setObjectMapperProps();
		this.javaType = javaType;
	}

	private void setObjectMapperProps() {
		this.objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
	}
	
	@SuppressWarnings("unchecked")
	public T deserialize(@Nullable byte[] bytes) {

		if (isEmpty(bytes)) {
			return null;
		}
		try {
			return (T) this.objectMapper.readValue(bytes, 0, bytes.length, javaType);
		} catch (Exception ex) {
			return (T) new String(bytes, DEFAULT_CHARSET);
		}
	}

	@Override
	public byte[] serialize(@Nullable Object t) throws SerializationException {

		if (t == null) {
			return EMPTY_ARRAY;
		}
		
		//如果是字符串，则以字符串形式序列化
		if (t instanceof String) {
			return ((String)t).getBytes(DEFAULT_CHARSET);
		} else if(t instanceof SimpleKey) {
			return StringPool.EMPTY.getBytes(DEFAULT_CHARSET);
		}
		
		try {
			return this.objectMapper.writeValueAsBytes(t);
		} catch (Exception ex) {
			throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
		}
	}

	/**
	 * Returns the Jackson {@link JavaType} for the specific class.
	 * <p>
	 * Default implementation returns {@link TypeFactory#constructType(java.lang.reflect.Type)}, but this can be
	 * overridden in subclasses, to allow for custom generic collection handling. For instance:
	 *
	 * <pre class="code">
	 * protected JavaType getJavaType(Class&lt;?&gt; clazz) {
	 * 	if (List.class.isAssignableFrom(clazz)) {
	 * 		return TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, MyBean.class);
	 * 	} else {
	 * 		return super.getJavaType(clazz);
	 * 	}
	 * }
	 * </pre>
	 *
	 * @param clazz the class to return the java type for
	 * @return the java type
	 */
	protected JavaType getJavaType(Class<?> clazz) {
		return TypeFactory.defaultInstance().constructType(clazz);
	}
	
	static boolean isEmpty(@Nullable byte[] data) {
		return (data == null || data.length == 0);
	}
	
	/**
     * 自定义ObjectMapper
     * 
     * @author kevin
     *
     */
	public class MyObjectMapper extends ObjectMapper {
        private static final long serialVersionUID = 1L;
 
        public MyObjectMapper() {
            super();
            // 去掉各种@JsonSerialize注解的解析
            this.configure(MapperFeature.USE_ANNOTATIONS, false);
            // 将类型序列化到属性json字符串中
            this.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
            // 对于找不到匹配属性的时候忽略报错
            this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            // 不包含任何属性的bean也不报错
            this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        }
    }
}
